package com.cskaoyan.java.object._4hashcode;

/**
 * 方法的声明
 * hashCode()方法的方法声明：
 * public native int hashCode();
 * 很显然，它是一个本地方法，这个方法也没有任何参数，返回值是一个int类型整数。
 *
 * 方法的作用:
 * 返回该对象的哈希码值
 * 支持此方法是为了提高哈希表的性能。
 *
 * 1.哈希映射
 * 哈希映射是一种特殊的映射，它要求：
 *      1.集合A必须是一个无限大小，具有无穷多元素的集合。
 *      2.集合B必须是一个元素有限的集合。
 * 哈希映射是"化无限为有限",这个过程中,可以出现多对一,但绝不可能出现一对多
 *
 * 2.哈希函数,哈希方法,哈希算法..
 * 在哈希映射当中，集合A和B之间的对应关系f，就是一种映射的规则,就是哈希算法
 *
 * 3.哈希值
 * 通过一个哈希算法,映射的对应规则f,把集合A上的元素,唯一的映射到集合B的一个元素
 * 集合B上的这个元素,就称之为哈希值
 *
 * 4.哈希冲突
 * 化无限为有限，这本身就是不现实的，更不可能强求映射是完全一一对应的。
 * 所以在哈希映射中，出现多对一是相当正常的，出现了多个哈希映射的哈希值是一样的,这种情况称之为"哈希冲突"。
 *
 * Java当中的哈希映射,是把一个类的全体对象(集合A,理论上是无限大的集合)映射到int类型整数这样一个有限大小的集合B上
 *
 * 1.哈希算法是什么?
 * hashCode()方法的具体实现就是哈希算法
 *
 * 2.哈希值是什么?
 * hashCode()方法的返回值就是哈希值,是一个int类型的整数
 *
 * 3.哈希冲突
 * 多个该类型的对象,调用hashCode方法,返回同一个整数,这时就出现了哈希冲突
 * 哈希冲突不可避免,只能减少
 *
 *
 * 解释以下问题:
 * 为什么支持hashCode方法能提升哈希表的性能?
 * 因为哈希表的设计中,如果能够尽量减少哈希冲突,那么处在链表中的元素就越少,哈希表的效率必然是越高的
 * 这时就需要依赖于hashCode方法的精巧设计,合理地哈希算法来减少哈希冲突
 *
 * 为什么equals方法和hashCode方法要求一起重写?
 * 哈希表是存储key-value的数据结构,其中key和value都是一个对象
 * key是一个唯一性标识,在哈希表中key必须是唯一的,不能重复
 * 那么如何判断两个key是重复还是不重复呢?
 * 用key所在类型的equals方法,这样对于哈希表的一个插入操作,就有了以下判断:
 *      1.如果equals方法判断新插入的key重复了,这时会覆盖掉原先的"key-value"
 *          -> 既然是覆盖,这时新增入的"key-value"和原先的"key-value"位置应该是一样的
 *          -> 因为位置是通过计算key对象的哈希值来计算的
 *          这时为了保证覆盖的位置一致,需要新插入的key对象和原先的key对象哈希值必须相同.
 *          --> 如果key1.equals(key2)返回true,那么要求key1.hashCode() = key2.hashCode();
 *          equals方法认定两个对象是相等的,那么两个对象调用hashCode方法的返回值必须是一样的
 *
 *          我们还可以从映射的定义来理解上述的结论:
 *          equals方法认定两个对象相等了,那么这两个对象就是映射集合A中的同一个元素
 *          这时映射的规则强制要求必须唯一的映射到集合B中的同一个元素(也就是哈希值必须一样)
 *
 *          结论(重要):
 *              1.如果一个类不重写equals和hashCode方法,equals方法是通过地址来判断对象相等的,hashCode方法也是同样根据地址来计算哈希值的,这样就能够保证equals方法返回true,哈希值必然一样.
 *              2.如果一个类重写了equals方法而不重写hashCode方法,是不能保证同true同相等的,这时就必须同时重写equals和hashCode两个方法.
 *              3.为了保证同true,同相等
 *              equals方法和hashCode方法的重写依据也必须是一样的,比如equals方法用age和score进行重写,那么hashCode方法也要依据这两个成员变量来进行重写
 *
 *      2.如果equals方法判断新插入的key不重复,这时就要将这个新的"key-value"进行插入操作了
 *          这时要计算key对象的哈希值,通过这个哈希值来计算出它在哈希表中的存储位置
 *          这时不强转要求它们的哈希值是不同的,因为哈希冲突是存在的,这时就有可能出现多对一
 *          但是
 *          作为一名程序员,应该要清楚，设计出更好的哈希算法，尽量避免哈希冲突，可以提升哈希表性能。
 *          虽然完美永远是达不到的，但我们要相信光。
 *
 * 以上hashCode方法就讲完了
 * 实际开发中如果需要用到哈希表,用到哈希映射,建议用IDE自动生成的哈希值计算算法
 * 只需要保证它们(equals方法和hashCode方法)重写的依据是一样的就够了
 *
 * 注意事项:
 * 一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。
 * 这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 *
 * 如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 * @since 15:42
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = s1;
        // System.out.println(s1.equals(s2));
        // System.out.println(s1.hashCode());
        // System.out.println(s2.hashCode());

        Student s3 = new Student(18, 100);
        System.out.println(s1.equals(s3));
        System.out.println(s1.hashCode());
        System.out.println(s3.hashCode());

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}